package com.example.gobang.controller;

import com.example.gobang.game.*;
import com.example.gobang.mapper.UserMapper;
import com.example.gobang.model.UserInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;

/**
 * Description:
 * Date: 2024-09-06
 * Time: 21:08
 */
@Slf4j
@Component
public class GameController extends TextWebSocketHandler {
    private Object lock = new Object();
    private ObjectMapper objectMapper = new ObjectMapper();

    @Autowired
    private RoomManager roomManager;

    @Autowired
    private OnlineUserManager onlineUserManager;

    @Resource
    private UserMapper userMapper;

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        GameReadyResponse resp = new GameReadyResponse();
        // 1.先获取用户身份信息（从 HttpSession 中拿到当前用户的对象）
        UserInfo userInfo = (UserInfo) session.getAttributes().get("userInfo");
        if (userInfo == null) {
            resp.setOk(false);
            resp.setReason("用户尚未登录");
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
            return;
        }
        // 2.判断当前用户是否已经进入房间（通过房间管理器查询）
        Room room = roomManager.getRoomByUserId(userInfo.getUserId());
        if (room == null) {
            // 若为空，说明没找到房间，也就是说这位玩家还没匹配到
            resp.setOk(false);
            resp.setReason("玩家尚未匹配到");
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
            return;
        }

        // 3.判断当前是不是多开（用到 onlineUserManager）
        // 这里约定，一个账号一边在游戏大厅，一边在游戏房间，这也视为是多开
        if (onlineUserManager.getFromGameHall(userInfo.getUserId()) != null
                || onlineUserManager.getFromGameRoom(userInfo.getUserId()) != null) {
            resp.setOk(true);
            resp.setReason("禁止多开游戏界面");
            resp.setMessage("repeatConnection");
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
            return;
        }

        // 4. 经过验证之后，就可以设置当前玩家上线
        onlineUserManager.enterGameRoom(userInfo.getUserId(),session);

        /**
         * 5.将两位玩家加入到游戏房间中
         * 前面创建房间/匹配对手的过程，是在 game_hall.html 中完成的
         * 所以前面匹配到对手之后需要跳转页面，来到 game_room.html 才算正式进入游戏房间
         * 意思就是说，当前这个方法的逻辑是在 game_room.html 页面加载的时候进行的
         * 执行到这里，说明页面跳转成功了
         */
        synchronized (lock) {
            if (room.getUser1() == null) {
                // 第一位玩家尚未加入房间，就把当前连上 WebSocket 的玩家作为 user1，加到房间中
                room.setUser1(userInfo);
                // 这里约定先进入房间的玩家先手
                room.setBlackUser(userInfo.getUserId());
                log.info("玩家[" + userInfo.getUserName() + "]准备就绪");
                return;
            }

            if (room.getUser2() == null) {
                // 逻辑执行到这里，说明玩家一已经进入房间，现在要把当前玩家设为玩家二
                room.setUser2(userInfo);
                log.info("玩家[" + userInfo.getUserName() + "]准备就绪");

                // 两玩家都进入房间后，要让服务器给这两位玩家返回 WebSocket 的响应数据
                // 通知两位玩家：游戏双方都已经准备好了
                // 通知玩家一
                noticeGameReady(room, room.getUser1(), room.getUser2());
                // 通知玩家二
                noticeGameReady(room, room.getUser2(), room.getUser1());
                return;
            }
        }
    }

    private void noticeGameReady(Room room,UserInfo thisUser,UserInfo thatUser) throws IOException {
        GameReadyResponse resp = new GameReadyResponse();
        resp.setMessage("gameReady");
        resp.setOk(true);
        resp.setReason("");
        resp.setRoomId(room.getRoomId());
        resp.setThisUserId(thisUser.getUserId());
        resp.setThatUserId(thatUser.getUserId());
        resp.setBlackUser(room.getBlackUser());
        // 将当前响应数据传给玩家
        WebSocketSession session = onlineUserManager.getFromGameRoom(thisUser.getUserId());
        session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
    }

    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        // 1.先从 Session 中拿到当前用户的身份信息
        UserInfo userInfo = (UserInfo) session.getAttributes().get("userInfo");
        if (userInfo == null) {
            log.info("当前玩家未登录");
            return;
        }
        // 2.根据玩家 id 获取到房间对象
        Room room = roomManager.getRoomByUserId(userInfo.getUserId());
        // 3.通过 room 对象来处理本次具体的请求
        room.putChess((String)message.getPayload());
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        UserInfo userInfo = (UserInfo) session.getAttributes().get("userInfo");
        if (userInfo == null) {
            // 这里就简单处理，在断开连接时就不给客户端返回响应
            return;
        }
        WebSocketSession exitSession = onlineUserManager.getFromGameRoom(userInfo.getUserId());
        if (session == exitSession) {
            // 加上这个判定是为了避免多开情况下第二个用户退出连接，导致第一个用户的会话被删除
            onlineUserManager.exitGameRoom(userInfo.getUserId());
        }
        log.info("玩家[" + userInfo.getUserName() + "]连接异常");

        // 通知对手获胜
        noticeThatUserWin(userInfo);
    }


    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        UserInfo userInfo = (UserInfo) session.getAttributes().get("userInfo");
        if (userInfo == null) {
            // 这里就简单处理，在断开连接时就不给客户端返回响应
            return;
        }
        WebSocketSession exitSession = onlineUserManager.getFromGameRoom(userInfo.getUserId());
        if (session == exitSession) {
            // 加上这个判定是为了避免多开情况下第二个用户退出连接，导致第一个用户的会话被删除
            onlineUserManager.exitGameRoom(userInfo.getUserId());
        }
        log.info("玩家[" + userInfo.getUserName() + "]离开游戏房间");

        // 通知对手获胜
        noticeThatUserWin(userInfo);
    }

    private void noticeThatUserWin(UserInfo userInfo) throws IOException {
        // 1.根据当前玩家找到其所在的房间
        Room room = roomManager.getRoomByUserId(userInfo.getUserId());
        if (room == null) {
            // 这个情况意味着房间已经没了
            log.info("当前房间已经释放，无需通知对手");
            return;
        }

        // 2.根据房间找到对手
        UserInfo thatUser = (userInfo == room.getUser1()) ? room.getUser2() : room.getUser1();
        // 3.找到对手的在线状态
        WebSocketSession session = onlineUserManager.getFromGameRoom(thatUser.getUserId());
        if (session == null) {
            // 这种情况说明对手也掉线了
            log.info("对手也掉线了，无需通知");
            return;
        }
        // 4.构造一个响应通知对手
        GameResponse response = new GameResponse();
        response.setMessage("putChess");
        response.setUserId(thatUser.getUserId());
        response.setWinner(thatUser.getUserId());
        session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));

        // 5.更新玩家分数信息
        int winUserId = thatUser.getUserId();
        int loseUserId = userInfo.getUserId();
        userMapper.userWin(winUserId);
        userMapper.userLose(loseUserId);

        // 6.释放房间资源
        roomManager.remove(room.getRoomId(),room.getUser1().getUserId(),room.getUser2().getUserId());
    }

}
